﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using CarShop.Domain.Entities;
using CarShop.Domain.Interfaces;
using CarShop.Domain.Interfaces.Repositories;
using CarShop.Security.Contracts;
using CarShop.Security.Contracts.Exceptions;
using CarShop.Security.Utils;

namespace CarShop.Security
{
    public class CarShopMembership : IMembershipProvider
    {
        private readonly IUnitOfWork _unitOfWork;
        private readonly IUserRepository _userRepository;
        private readonly IRoleRepository _roleRepository;


        public CarShopMembership(IUnitOfWork unitOfWork, IUserRepository userRepository, IRoleRepository roleRepository)
        {
            _unitOfWork = unitOfWork;
            _userRepository = userRepository;
            _roleRepository = roleRepository;
        }


        public User CreateUser(string login, string email, string password)
        {
            User existingUser = FindUserByLoginOrEmail(login) ?? FindUserByLoginOrEmail(email);

            if (existingUser != null)
            {
                throw new UserAlreadyExistsException(login);
            }

            SaltedHashedPassword hashInfo = new SaltedHashedPassword(password);

            User user = new User
            {
                Login = login,
                Email = email,
                PasswordHash = hashInfo.Hash,
                PasswordSalt = hashInfo.Salt,
                LastActivityDate = DateTime.UtcNow,
                UpdateDate = DateTime.UtcNow,
                CreationDate = DateTime.UtcNow,
                UpdateLogin = login
            };
            _userRepository.Add(user);
            _unitOfWork.Commit();
            return user;
        }

        public User FindUserById(int userId)
        {
            var result = _userRepository.Get(userId);
            return result;
        }

        public User FindUserByLoginOrEmail(string loginOrEmail)
        {
            var result =
                _userRepository.GetAll("Roles").FirstOrDefault(u => u.Login == loginOrEmail || u.Email == loginOrEmail);
            return result;
        }

        public Role FindRoleByName(string roleName)
        {
            var result = _roleRepository.GetAll().FirstOrDefault(r => r.Name == roleName);

            return result;
        }

        public string ResetPassword(string loginOrEmail)
        {
            var user = FindUserByLoginOrEmail(loginOrEmail);

            if (user == null)
            {
                throw new UserNotExistsException(loginOrEmail);
            }

            var newPassword = GeneratePassword(8, true);
            ChangePassword(newPassword, user.Id);
            return newPassword;
        }

        public void ChangePassword(string newPassword, int userId)
        {
            var user = FindUserById(userId);
            SaltedHashedPassword hashInfo = new SaltedHashedPassword(newPassword);

            user.PasswordHash = hashInfo.Hash;
            user.PasswordSalt = hashInfo.Salt;
            _userRepository.Update(user);
            _unitOfWork.Commit();
        }

        public void ValidateUser(string loginOrEmail, string password)
        {
            var user = FindUserByLoginOrEmail(loginOrEmail);

            if (user == null)
            {
                throw new UserValidationException(ValidationFaultReason.UserNotFound);
            }

            if (VerifyPassword(user, password))
            {
                user.LastActivityDate = DateTime.UtcNow;
                _userRepository.Update(user);
                _unitOfWork.Commit();
            }
            else
            {
                throw new UserValidationException(ValidationFaultReason.InvalidPassword);
            }
        }

        public void AddUserToRole(int userId, string roleName)
        {
            var user = FindUserById(userId);

            if (user == null)
            {
                throw new MembershipProviderException("User with id {0} not found.", userId);
            }

            var role = FindRoleByName(roleName) ?? new Role { Name = roleName, CreationDate = DateTime.UtcNow };
            user.Roles.Add(role);
            _userRepository.Update(user);
            _unitOfWork.Commit();
        }

        private string GeneratePassword(int passwordLength, bool strongPassword)
        {
            var random = new Random(DateTime.UtcNow.Millisecond);
            const string allowedChars = "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789";
            const string specialCharacters = @"!#$%&'()*+,-./:;<=>?@[\]_";

            var chars = new char[passwordLength];

            for (var i = 0; i < passwordLength; i++)
            {
                if (strongPassword && i % random.Next(3, passwordLength) == 0)
                {
                    chars[i] = specialCharacters[random.Next(0, specialCharacters.Length)];
                }
                else
                {
                    chars[i] = allowedChars[random.Next(0, allowedChars.Length)];
                }
            }

            return new string(chars);
        }

        private static bool VerifyPassword(User user, string password)
        {
            var isValid = SaltedHashedPassword.Verify(user.PasswordSalt, user.PasswordHash,password);
            return isValid;
        }
    }
}
